﻿using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace CAF.Infrastructure.Core.Data
{
    
    public static class RepositoryExtensions
    {

        public static IEnumerable<T> LoadAll<T>(this IRepository<T> rs) where T : BaseEntity
        {
            return rs.Table.AsEnumerable();
        }

        public static IEnumerable<T> Where<T>(this IRepository<T> rs, Func<T, bool> predicate) where T : BaseEntity
        {
            return rs.Table.Where(predicate);
        }

        public static T GetSingle<T>(this IRepository<T> rs, Func<T, bool> predicate) where T : BaseEntity
        {
            return rs.Table.SingleOrDefault(predicate);
        }

        public static T GetFirst<T>(this IRepository<T> rs, Func<T, bool> predicate) where T : BaseEntity
        {
            return rs.Table.FirstOrDefault(predicate);
        }

        public static IEnumerable<T> GetMany<T>(this IRepository<T> rs, IEnumerable<int> ids) where T : BaseEntity
        {
            foreach (var chunk in ids.Chunk())
            {
                var query = rs.Table.Where(a => chunk.Contains(a.Id));
                foreach (var item in query)
                {
                    yield return item;
                }
            }
        }

        public static void Delete<T>(this IRepository<T> rs, int id) where T : BaseEntity
        {
			Guard.ArgumentNotZero(id, "id");
			
			// Perf: work with stub entity
			var entity = rs.Create();
			entity.Id = id;

			rs.Attach(entity);

			// must downcast 'cause of Rhino mocks stub  
			rs.Context.ChangeState((BaseEntity)entity, System.Data.Entity.EntityState.Deleted);
        }

		public static void DeleteRange<T>(this IRepository<T> rs, IEnumerable<int> ids) where T : BaseEntity
		{
			Guard.ArgumentNotNull(() => ids);

			ids.Each(id => rs.Delete(id));
		}

		/// <summary>
		/// Truncates the table
		/// </summary>
		/// <typeparam name="T">Entity type</typeparam>
		/// <param name="rs">The repository</param>
		/// <param name="predicate">An optional filter</param>
		/// <param name="cascade">
		/// <c>false</c>: does not make any attempts to determine dependant entities, just deletes ONLY them (faster).
		/// <c>true</c>: loads all entities into the context first and deletes them, along with their dependencies (slower).
		/// </param>
		/// <returns>The total number of affected entities</returns>
		/// <remarks>
		/// This method turns off auto detection, validation and hooking.
		/// </remarks>
		[SuppressMessage("ReSharper", "UnusedVariable")]
		public static int DeleteAll<T>(this IRepository<T> rs, Expression<Func<T, bool>> predicate = null, bool cascade = false) where T : BaseEntity
		{
			var count = 0;

			using (var scope = new DbContextScope(ctx: rs.Context, autoDetectChanges: false, validateOnSave: false, hooksEnabled: false, autoCommit: false))
			{
				var query = rs.Table;
				if (predicate != null)
				{
					query = query.Where(predicate);
				}

				if (cascade)
				{
					var records = query.ToList();
					foreach (var chunk in records.Chunk(500))
					{
						rs.DeleteRange(chunk.ToList());
						count += rs.Context.SaveChanges();
					}
				}
				else
				{
					var ids = query.Select(x => new { Id = x.Id }).ToList();
					foreach (var chunk in ids.Chunk(500))
					{
						rs.DeleteRange(chunk.Select(x => x.Id));
						count += rs.Context.SaveChanges();
					}
				}
			}

			return count;
		}

        public static void BulkInsertAll<T>(IEnumerable<T> entities)
        {
            entities = entities.ToArray();
            var conn = new SqlConnection(DataSettings.Current.DataConnectionString);
            conn.Open();

            Type t = typeof(T);

            var bulkCopy = new SqlBulkCopy(conn)
            {
                DestinationTableName = t.Name
            };

            var properties = t.GetProperties().Where(EventTypeFilter).ToArray();
            var table = new DataTable();

            foreach (var property in properties)
            {
                Type propertyType = property.PropertyType;
                if (propertyType.IsGenericType &&
                    propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
                {
                    propertyType = Nullable.GetUnderlyingType(propertyType);
                }

                table.Columns.Add(new DataColumn(property.Name, propertyType));
            }

            foreach (var entity in entities)
            {
                table.Rows.Add(properties.Select(
                  property => GetPropertyValue(
                  property.GetValue(entity, null))).ToArray());
            }

            bulkCopy.WriteToServer(table);
            conn.Close();
        }

        private static bool EventTypeFilter(System.Reflection.PropertyInfo p)
        {
            var attribute = Attribute.GetCustomAttribute(p,
                typeof(AssociationAttribute)) as AssociationAttribute;

            if (attribute == null) return true;
            if (attribute.IsForeignKey == false) return true;

            return false;
        }

        private static object GetPropertyValue(object o)
        {
            if (o == null)
                return DBNull.Value;
            return o;
        }

        public static IQueryable<T> Get<T>(
            this IRepository<T> rs,
            Expression<Func<T, bool>> predicate = null,
            Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null,
            string includeProperties = "") where T : BaseEntity
        {
            IQueryable<T> query = rs.Table;

            if (predicate != null)
            {
                query = query.Where(predicate);
            }

            foreach (var includeProperty in includeProperties.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
            {
				query = query.Expand(includeProperty.Trim());
            }

            if (orderBy != null)
            {
                return orderBy(query);
            }
            else
            {
                return query;
            }
        }


    }

}
